{********************************************************************}
{                                                                    }
{       Developer Express Visual Component Library                   }
{       ExpressEditors                                               }
{                                                                    }
{       Copyright (c) 1998-2014 Developer Express Inc.               }
{       ALL RIGHTS RESERVED                                          }
{                                                                    }
{   The entire contents of this file is protected by U.S. and        }
{   International Copyright Laws. Unauthorized reproduction,         }
{   reverse-engineering, and distribution of all or any portion of   }
{   the code contained in this file is strictly prohibited and may   }
{   result in severe civil and criminal penalties and will be        }
{   prosecuted to the maximum extent possible under the law.         }
{                                                                    }
{   RESTRICTIONS                                                     }
{                                                                    }
{   THIS SOURCE CODE AND ALL RESULTING INTERMEDIATE FILES            }
{   (DCU, OBJ, DLL, ETC.) ARE CONFIDENTIAL AND PROPRIETARY TRADE     }
{   SECRETS OF DEVELOPER EXPRESS INC. THE REGISTERED DEVELOPER IS    }
{   LICENSED TO DISTRIBUTE THE EXPRESSEDITORS AND ALL                }
{   ACCOMPANYING VCL CONTROLS AS PART OF AN EXECUTABLE PROGRAM ONLY. }
{                                                                    }
{   THE SOURCE CODE CONTAINED WITHIN THIS FILE AND ALL RELATED       }
{   FILES OR ANY PORTION OF ITS CONTENTS SHALL AT NO TIME BE         }
{   COPIED, TRANSFERRED, SOLD, DISTRIBUTED, OR OTHERWISE MADE        }
{   AVAILABLE TO OTHER INDIVIDUALS WITHOUT EXPRESS WRITTEN CONSENT   }
{   AND PERMISSION FROM DEVELOPER EXPRESS INC.                       }
{                                                                    }
{   CONSULT THE END USER LICENSE AGREEMENT FOR INFORMATION ON        }
{   ADDITIONAL RESTRICTIONS.                                         }
{                                                                    }
{********************************************************************}

unit dxXMLDoc;

{$I cxVer.inc}

interface

uses
  Classes, dxCore, dxCoreClasses, cxClasses, cxControls, SysUtils, dxCustomTree,
  Windows, StrUtils {$IFDEF DELPHI12}, AnsiStrings {$ENDIF};

type
  TdxXMLNode = class;
  TdxXMLNodeAttribute = class;
  TdxXMLNodeAttributes = class;
  TdxXMLDocument = class;

  TdxXMLString = type AnsiString;

  TdxXMLEncoding = (dxxeNone, dxxeUTF8, dxxeWindows);
  TdxXMLTokenID = (ttUknown, ttEqual, ttTagHeaderBegin, ttTagHeaderEnd, ttTagEnd, ttTagFooter, ttComment);

  TdxXMLToken = packed record
    Buffer: PAnsiChar;
    BufferLengthInChars: Integer;
    TokenType: TdxXMLTokenID;
  end;

  { TdxXMLParser }

  TdxXMLParser = class(TObject)
  private
    FData: PAnsiChar;
    FDataLength: Integer;
    FDocument: TdxXMLDocument;
    FEncoding: TdxXMLEncoding;
    FEncodingCodePage: Integer;

    function NextToken(out AToken: TdxXMLToken): Boolean; overload;
    function NextToken(var P: PAnsiChar; var C: Integer; out AToken: TdxXMLToken): Boolean; overload;
    function TokenToString(const AToken: TdxXMLToken): AnsiString;
  protected
    function DecodeValue(const S: AnsiString): TdxXMLString;
    procedure ParseDocumentHeader;
    procedure ParseEncoding;
    function ParseNodeHeader(ANode: TdxXMLNode): TdxXMLNode;
    procedure ParseNodeValue(ANode: TdxXMLNode; ATagHeaderEndCursor, ACursor: PAnsiChar);
  public
    constructor Create(ADocument: TdxXMLDocument);
    procedure Parse(AScan: PAnsiChar; ACount: Integer);
    //
    property Document: TdxXMLDocument read FDocument;
  end;

  { TdxXMLHelper }

  TdxXMLHelper = class
  protected
    class function IsBoolean(const S: TdxXMLString): Boolean;
    class function IsPreserveSpacesNeeded(const S: TdxXMLString): Boolean;
  public
    class function DecodeBoolean(const S: string): Boolean;
    class function DecodeString(const S: TdxXMLString): TdxXMLString;
    class function EncodeBoolean(const Value: Boolean): TdxXMLString;
    class function EncodeString(const S: TdxXMLString; ARemoveBreakLines: Boolean): TdxXMLString;
  end;

  { TdxXMLNodeAttribute }

  TdxXMLNodeAttribute = class(TcxDoublyLinkedObject)
  private
    FName: TdxXMLString;
    function GetValue: TdxXMLString;
    function GetValueAsBoolean: Boolean; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetValueAsFloat: Double;
    function GetValueAsInteger: Integer;
    function GetValueAsString: string; {$IFDEF DELPHI9}inline;{$ENDIF}
    procedure SetValue(const Value: TdxXMLString);
    procedure SetValueAsBoolean(AValue: Boolean); {$IFDEF DELPHI9}inline;{$ENDIF}
    procedure SetValueAsFloat(const AValue: Double);
    procedure SetValueAsInteger(AValue: Integer);
    procedure SetValueAsString(const AValue: string); {$IFDEF DELPHI9}inline;{$ENDIF}
  protected
    FValue: TdxXMLString;
  public
    function GetDataSize: Integer;
    function ToAnsiString: TdxXMLString;

    property Name: TdxXMLString read FName write FName;
    property Value: TdxXMLString read GetValue write SetValue;
    property ValueAsBoolean: Boolean read GetValueAsBoolean write SetValueAsBoolean;
    property ValueAsFloat: Double read GetValueAsFloat write SetValueAsFloat;
    property ValueAsInteger: Integer read GetValueAsInteger write SetValueAsInteger;
    property ValueAsString: string read GetValueAsString write SetValueAsString;
  end;

  { TdxXMLNodeAttributes }

  TdxXMLNodeAttributes  = class(TcxDoublyLinkedObjectList)
  private
    FCount: Integer;
    FNode: TdxXMLNode;

    function GetFirst: TdxXMLNodeAttribute;
    function GetLast: TdxXMLNodeAttribute;
  protected
    function CreateLinkedObject: TcxDoublyLinkedObject; override;
    function GetAsText: TdxXMLString;
    function GetAttr(const AAttrName: TdxXMLString): TdxXMLNodeAttribute;
  public
    function Add: TcxDoublyLinkedObject; overload; override;
    function Add(const AttrName: TdxXMLString): TdxXMLNodeAttribute; reintroduce; overload;
    function Add(const AttrName: TdxXMLString; AValue: Boolean): TdxXMLNodeAttribute; reintroduce; overload;
    function Add(const AttrName: TdxXMLString; AValue: Integer): TdxXMLNodeAttribute; reintroduce; overload;
    function Add(const AttrName: TdxXMLString; const AValue: TdxUnicodeString): TdxXMLNodeAttribute; reintroduce; overload;
    function Add(const AttrName: TdxXMLString; const AValue: TdxXMLString): TdxXMLNodeAttribute; reintroduce; overload;
    procedure Delete(const AAttrName: TdxXMLString); reintroduce;
    function Find(const AAttrName: TdxXMLString; out AAttr: TdxXMLNodeAttribute): Boolean;
    procedure Remove(ALinkedObject: TcxDoublyLinkedObject); override;
    
    function GetValue(const AAttrName: TdxXMLString; const ADefaultValue: TdxXMLString = ''): TdxXMLString;
    function GetValueAsBoolean(const AAttrName: TdxXMLString; ADefaultValue: Boolean = False): Boolean;
    function GetValueAsFloat(const AAttrName: TdxXMLString; const ADefaultValue: Double = 0): Double;
    function GetValueAsInteger(const AAttrName: TdxXMLString; ADefaultValue: Integer = 0): Integer;
    function GetValueAsString(const AAttrName: TdxXMLString): string; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetValueAsUnicodeString(const AAttrName: TdxXMLString): TdxUnicodeString; {$IFDEF DELPHI9}inline;{$ENDIF}
    procedure SetValue(const AAttrName: TdxXMLString; const AValue: TdxXMLString);
    procedure SetValueAsBoolean(const AAttrName: TdxXMLString; AValue: Boolean);
    procedure SetValueAsFloat(const AAttrName: TdxXMLString; const AValue: Double);
    procedure SetValueAsInteger(const AAttrName: TdxXMLString; AValue: Integer);
    procedure SetValueAsString(const AAttrName: TdxXMLString; const AValue: string);
    procedure SetValueAsUnicodeString(const AAttrName: TdxXMLString; const AValue: TdxUnicodeString);

    property Count: Integer read FCount;
    property First: TdxXMLNodeAttribute read GetFirst;
    property Last: TdxXMLNodeAttribute read GetLast;
    property Node: TdxXMLNode read FNode;
  end;

  { TdxXMLNode }

  TdxXMLNodeForEachProc = procedure (ANode: TdxXMLNode; AUserData: Pointer) of object;

  TdxXMLNode = class(TdxTreeCustomNode)
  private
    FAttributes: TdxXMLNodeAttributes;
    FName: TdxXMLString;
    FNamespaceURI: TdxXMLString;
    FText: TdxXMLString;

    function GetAttributesAsText: TdxXMLString;
    function GetDocument: TdxXMLDocument;
    function GetFirst: TdxXMLNode; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetItem(Index: Integer): TdxXMLNode; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetNext: TdxXMLNode; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetParent: TdxXMLNode; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetText: TdxXMLString; {$IFDEF DELPHI9}inline;{$ENDIF}
    function GetTextAsUnicodeString: TdxUnicodeString; {$IFDEF DELPHI9}inline;{$ENDIF}
    procedure SetText(const AValue: TdxXMLString);
    procedure SetTextAsUnicodeString(const Value: TdxUnicodeString);
  protected
    procedure CheckEncodedText(var AText: TdxXMLString); virtual;
    function GetAttributesSize: Integer; virtual;
    function GetChildrenSize: Integer; virtual;
    function GetDataSize: Integer; virtual;
    function HasData: Boolean;
    procedure ReadData(AStream: TStream); override;
    procedure WriteAttributes(AStream: TStream); virtual;
    procedure WriteChildren(AStream: TStream); virtual;
    procedure WriteData(AStream: TStream); override;
    procedure WriteString(AStream: TStream; const AString: TdxXMLString);
  public
    constructor Create(AOwner: IdxTreeOwner); override;
    destructor Destroy; override;
    function AddChild(const ATagName, ANamespaceURI: TdxXMLString): TdxXMLNode; overload;
    function AddChild(const ATagName: TdxXMLString): TdxXMLNode; overload;
    procedure Clear; override;
    procedure SetAttribute(const AttrName: TdxXMLString; const AValue: Variant);

    function FindChild(const AName: TdxXMLString): TdxXMLNode; overload;
    function FindChild(const AName: TdxXMLString; out ANode: TdxXMLNode): Boolean; overload;
    function FindChild(const ANamesHierarchy: array of TdxXMLString; out ANode: TdxXMLNode): Boolean; overload;
    procedure ForEach(AProc: TdxXMLNodeForEachProc; AUserData: Pointer);

    property Attributes: TdxXMLNodeAttributes read FAttributes;
    property AttributesAsText: TdxXMLString read GetAttributesAsText;
    property Name: TdxXMLString read FName; // tag name
    property NamespaceURI: TdxXMLString read FNamespaceURI;
    property Text: TdxXMLString read GetText write SetText;
    property TextAsUnicodeString: TdxUnicodeString read GetTextAsUnicodeString write SetTextAsUnicodeString;

    property Items[Index: Integer]: TdxXMLNode read GetItem; default;
    property Document: TdxXMLDocument read GetDocument;
    property First: TdxXMLNode read GetFirst;
    property Next: TdxXMLNode read GetNext;
    property Parent: TdxXMLNode read GetParent;
  end;

  { TdxXMLRootNode }

  TdxXMLRootNode = class(TdxXMLNode)
  protected
    function GetDataSize: Integer; override;
    function GetHeaderText: TdxXMLString;
    procedure WriteData(AStream: TStream); override;
  end;

  { TdxXMLDocument }

  TdxXMLDocument = class(TcxInterfacedPersistent, IdxTreeOwner)
  private
    FEncoding: TdxXMLString;
    FRoot: TdxXMLNode;
    FStandAlone: TdxXMLString;
    FVersion: TdxXMLString;
    function IdxTreeOwner.GetOwner = GetNodesOwner;
  protected
    procedure BeforeDelete(ASender: TdxTreeCustomNode);
    procedure BeginUpdate;
    function CanCollapse(ASender: TdxTreeCustomNode): Boolean;
    function CanExpand(ASender: TdxTreeCustomNode): Boolean;
    procedure Collapsed(ASender: TdxTreeCustomNode);
    procedure DeleteNode(ASender: TdxTreeCustomNode);
    procedure EndUpdate;
    procedure Expanded(ASender: TdxTreeCustomNode);
    function GetNodeClass(ARelativeNode: TdxTreeCustomNode): TdxTreeCustomNodeClass; virtual;
    function GetNodesOwner: TPersistent;  
    procedure LoadChildren(ASender: TdxTreeCustomNode);
    procedure TreeNotification(ASender: TdxTreeCustomNode; ANotification: TdxTreeNodeNotifications);
  public
    constructor Create(AOwner: TPersistent); override;
    destructor Destroy; override;
    function AddChild(const ATagName: TdxXMLString): TdxXMLNode; overload;
    function AddChild(const ATagName, ANamespaceURI: TdxXMLString): TdxXMLNode; overload;
    function FindChild(const AName: TdxXMLString; out ANode: TdxXMLNode): Boolean; overload;
    function FindChild(const ANamesHierarchy: array of TdxXMLString; out ANode: TdxXMLNode): Boolean; overload;

    procedure LoadFromFile(const AFileName: TFileName);
    procedure LoadFromStream(AStream: TStream);
    procedure SaveToFile(const AFileName: TFileName);
    procedure SaveToStream(AStream: TStream);

    property Encoding: TdxXMLString read FEncoding write FEncoding;
    property Root: TdxXMLNode read FRoot;
    property Standalone: TdxXMLString read FStandAlone write FStandAlone;
    property Version: TdxXMLString read FVersion write FVersion;
  end;

function dxUnicodeStringToXMLString(const AValue: TdxUnicodeString): TdxXMLString; {$IFDEF DELPHI9}inline;{$ENDIF}
function dxWideStringToXMLString(const AValue: WideString): TdxXMLString; {$IFDEF DELPHI9}inline;{$ENDIF}
function dxXMLStringToString(const AValue: TdxXMLString): string; {$IFDEF DELPHI9}inline;{$ENDIF}
function dxXMLStringToUnicodeString(const AValue: TdxXMLString): TdxUnicodeString; {$IFDEF DELPHI9}inline;{$ENDIF}
implementation

uses
  SysConst;

const
  sdxDefaultXMLVersion = '1.0';

  sAttributeEncoding = 'encoding';
  sAttributeVersion = 'version';
  sEncodingUTF8 = 'UTF-8';
  sEncodingWindows = 'Windows-';

  sXMLSpaceModeAttr = AnsiString('xml:space');
  sXMLSpaceModePreserve = AnsiString('preserve');

  sXMLBoolValues: array[Boolean] of TdxXMLString = ('false', 'true');

  XMLServiceCharMapCount = 7;
  XMLServiceCharMap: array [0..XMLServiceCharMapCount - 1, 0..1] of AnsiString =
  (
    (#9, '&#9;'),
    (#10, '&#10;'),
    (#13, '&#13;'),
    ('"', '&quot;'),
    ('<', '&lt;'),
    ('>', '&gt;'),
    ('&', '&amp;') 
  );

function FindDataInMemory(const AData, AMem: PByte; ADataSize, AMemSize, AMemOffset: Integer; out AOffset: Integer): Boolean;
var
  P: PByte;
  C: Integer;
begin
  Result := False;
  P := PByte(TdxNativeInt(AMem) + AMemOffset);
  C := AMemSize - AMemOffset;
  while C >= ADataSize do
  begin
    Result := (PByteArray(P)^[0] = PByteArray(AData)^[0]) and
      (PByteArray(P)^[ADataSize - 1] = PByteArray(AData)^[ADataSize - 1]) and
       CompareMem(P, AData, ADataSize);
    if Result then
    begin
      AOffset := AMemSize - C;
      Break;
    end;
    Dec(C);
    Inc(P);
  end;
end;

function FindStringInMemoryA(const S: AnsiString; AMem: PByte; AMemSize, AMemOffset: Integer; out AOffset: Integer): Boolean;
begin
  Result := FindDataInMemory(@S[1], AMem, Length(S), AMemSize, AMemOffset, AOffset);
end;

function dxWideStringToXMLString(const AValue: WideString): TdxXMLString; {$IFDEF DELPHI9}inline;{$ENDIF}
begin
  Result := dxWideStringToAnsiString(AValue, CP_UTF8);
end;

function dxUnicodeStringToXMLString(const AValue: TdxUnicodeString): TdxXMLString; {$IFDEF DELPHI9}inline;{$ENDIF}
begin
{$IFDEF UNICODE}
  Result := dxStringToAnsiString(AValue, CP_UTF8);
{$ELSE}
  Result := dxWideStringToAnsiString(AValue, CP_UTF8);
{$ENDIF}
end;

function dxXMLStringToString(const AValue: TdxXMLString): string; {$IFDEF DELPHI9}inline;{$ENDIF}
begin
  Result := dxAnsiStringToString(AValue, CP_UTF8);
end;

function dxXMLStringToUnicodeString(const AValue: TdxXMLString): TdxUnicodeString; {$IFDEF DELPHI9}inline;{$ENDIF}
begin
{$IFDEF UNICODE}
  Result := dxAnsiStringToString(AValue, CP_UTF8);
{$ELSE}
  Result := dxAnsiStringToWideString(AValue, CP_UTF8);
{$ENDIF}
end;

{ TdxXMLParser }

constructor TdxXMLParser.Create(ADocument: TdxXMLDocument);
begin
  inherited Create;
  FDocument := ADocument;
end;

procedure TdxXMLParser.Parse(AScan: PAnsiChar; ACount: Integer);
var
  ANode: TdxXMLNode;
  ATagHeaderEndCursor: PAnsiChar;
  AToken: TdxXMLToken;
begin
  FData := AScan;
  FDataLength := ACount;

  ANode := Document.Root;
  ANode.Clear;

  ATagHeaderEndCursor := nil;
  while NextToken(AToken) do
  begin
    case AToken.TokenType of
      ttTagHeaderBegin:
        if (FDataLength > 0) and (FData^ = '?') then
          ParseDocumentHeader
        else
        begin
          ANode := ParseNodeHeader(ANode.AddChild(''));
          if ANode = nil then Break;
          ATagHeaderEndCursor := FData;
        end;

      ttTagFooter:
        begin
          if ANode.Count = 0 then
            ParseNodeValue(ANode, ATagHeaderEndCursor, FData - AToken.BufferLengthInChars);
          ANode := ANode.Parent;
          if ANode = nil then Break;
          ATagHeaderEndCursor := nil;
        end;
    end;
  end;
end;

function TdxXMLParser.DecodeValue(const S: AnsiString): TdxXMLString;
begin
  case FEncoding of
    dxxeWindows:
      Result := dxUnicodeStringToXMLString(dxAnsiStringToWideString(S, FEncodingCodePage));
  else
    Result := S;
  end;
end;

procedure TdxXMLParser.ParseDocumentHeader;
var
  AAttr: TdxXMLNodeAttribute;
  ANode: TdxXMLNode;
begin
  ANode := TdxXMLNode.Create(Document);
  try
    ParseNodeHeader(ANode);

    if ANode.Attributes.Find(sAttributeEncoding, AAttr) then
      Document.Encoding := AAttr.Value
    else
      Document.Encoding := sEncodingUTF8;

    if ANode.Attributes.Find(sAttributeVersion, AAttr) then
      Document.Version := AAttr.Value
    else
      Document.Version := sdxDefaultXMLVersion;

    ParseEncoding;
  finally
    ANode.Free;
  end;
end;

procedure TdxXMLParser.ParseEncoding;
var
  AEncodingValue: string;
begin
  AEncodingValue := dxAnsiStringToString(Document.Encoding);
  if SameText(AEncodingValue, sEncodingUTF8) then
    FEncoding := dxxeUTF8
  else
    if SameText(Copy(AEncodingValue, 1, Length(sEncodingWindows)), sEncodingWindows) then
    begin
      FEncoding := dxxeWindows;
      FEncodingCodePage := StrToIntDef(Copy(AEncodingValue, Length(sEncodingWindows) + 1, MaxInt), 0);
    end
    else
      FEncoding := dxxeNone;
end;

procedure TdxXMLParser.ParseNodeValue(ANode: TdxXMLNode; ATagHeaderEndCursor, ACursor: PAnsiChar);
var
  ALength: Integer;
  AValue: AnsiString;
  S1, S2: PAnsiChar;
begin
  if ATagHeaderEndCursor <> nil then
  begin
    S2 := ACursor - 1;
    S1 := ATagHeaderEndCursor;
    ALength := NativeUInt(S2) - NativeUInt(S1) + 1;
    if not SameText(ANode.Attributes.GetValue(sXMLSpaceModeAttr), sXMLSpaceModePreserve) then
    begin
      while (S1^ <= ' ') and (ALength > 0) do
      begin
        Dec(ALength);
        Inc(S1);
      end;
      while (S2^ <= ' ') and (ALength > 0) do
      begin
        Dec(ALength);
        Dec(S2);
      end;
    end;
    if ALength > 0 then
    begin
      SetString(AValue, S1, ALength);
      ANode.FText := AValue;
    end;
  end;
end;

function TdxXMLParser.ParseNodeHeader(ANode: TdxXMLNode): TdxXMLNode;
var
  AToken: TdxXMLToken;
  ATokenIndex: Integer;
begin
  ATokenIndex := 0;
  Result := ANode.Parent;
  while NextToken(AToken) do
  begin
    case AToken.TokenType of
      ttTagEnd:
        Break;
      ttTagHeaderEnd:
        begin
          Result := ANode;
          Break;
        end;
      else
        begin
          if (ATokenIndex > 3) then
            ATokenIndex := 1;
          if (ATokenIndex = 0) then
            ANode.FName := TokenToString(AToken);
          if (ATokenIndex = 2) and (AToken.TokenType <> ttEqual) then
            ATokenIndex := 1;
          if (ATokenIndex = 1) then
            ANode.Attributes.Add(TokenToString(AToken), '');
          if (ATokenIndex = 3) then
            ANode.Attributes.Last.FValue := TokenToString(AToken);
        end;
    end;
    Inc(ATokenIndex);
  end;
end;

function TdxXMLParser.NextToken(out AToken: TdxXMLToken): Boolean;
begin
  Result := NextToken(FData, FDataLength, AToken);
end;

function TdxXMLParser.NextToken(var P: PAnsiChar; var C: Integer; out AToken: TdxXMLToken): Boolean;

  function IsSpace(const A: AnsiChar): LongBool; {$IFDEF DELPHI9}inline;{$ENDIF}
  begin
    Result := (A = ' ') or (A = #9) or (A = #13)  or (A = #10);
  end;

  function IsQuot(const A: AnsiChar): LongBool; {$IFDEF DELPHI9}inline;{$ENDIF}
  begin
    Result := (A = '"') or (A = #39);
  end;

  function IsTagDelimiter(const A: AnsiChar): LongBool; {$IFDEF DELPHI9}inline;{$ENDIF}
  begin
    Result := (A = '<') or (A = '>');
  end;

  function IsDelimiter(const A: AnsiChar): LongBool; {$IFDEF DELPHI9}inline;{$ENDIF}
  begin
    Result := (A = '=') or (A = '/') or IsTagDelimiter(A) or IsQuot(A) or IsSpace(A);
  end;

  procedure MoveToNextSymbol;
  begin
    if C > 0 then
    begin
      Inc(P);
      Dec(C);
    end;
  end;

  procedure MoveUntilQuotOrTag(AQuot: AnsiChar);
  begin
    while (C > 0) and (P^ <> AQuot) do
    begin
      if IsTagDelimiter(P^) then
      begin
        Dec(P);
        Inc(C);
        Break;
      end;
      Inc(P);
      Dec(C);
    end;
  end;

  procedure MoveUntilDelimiter;
  begin
    while (C > 0) and not IsDelimiter(P^) do
    begin
      Inc(P);
      Dec(C);
    end;
  end;

  procedure SkipSpaces;
  begin
    while (C > 0) and IsSpace(P^) do
    begin
      Inc(P);
      Dec(C);
    end;
  end;

  procedure PutSpecialToken(AType: TdxXMLTokenID; ALength: Integer);
  begin
    AToken.Buffer := P;
    AToken.BufferLengthInChars := ALength;
    AToken.TokenType := AType;
    Dec(C, ALength);
    Inc(P, ALength);
  end;

  function CheckForCommentToken(out ALength: Integer): Boolean;

    function DoCheck(const AStartID, AFinishID: AnsiString): Boolean;
    var
      LS, LF: Integer;
    begin
      Result := False;
      LS := Length(AStartID);
      LF := Length(AFinishID);
      if (C > LS + LF) and CompareMem(P, @AStartID[1], LS) then
      begin
        Result := FindStringInMemoryA(AFinishID, PByte(P), C, LS, ALength);
        if Result then
          Inc(ALength, LF);
      end;
    end;

  begin
    Result := DoCheck('<!--', '-->') or DoCheck('<![CDATA[', ']]>');
  end;

  function CheckForSpecialToken: Boolean;
  var
    ALength: Integer;
  begin
    case P^ of
      '<':
        if (C > 1) and (PAnsiChar(P + 1)^ = '/') then
          PutSpecialToken(ttTagFooter, 2)
        else
          if (C > 1) and (PAnsiChar(P + 1)^ = '!') and CheckForCommentToken(ALength) then
            PutSpecialToken(ttComment, ALength)
          else
            PutSpecialToken(ttTagHeaderBegin, 1);

      '/', '?':
        if (C > 1) and (PAnsiChar(P + 1)^ = '>') then
          PutSpecialToken(ttTagEnd, 2);
      '=':
        PutSpecialToken(ttEqual, 1);
      '>':
        PutSpecialToken(ttTagHeaderEnd, 1);
    end;
    Result := AToken.TokenType <> ttUknown;
  end;

var
  AQuot: AnsiChar;
begin
  SkipSpaces;
  AToken.TokenType := ttUknown;
  AToken.BufferLengthInChars := 0;
  Result := C > 0;
  if Result then
  begin
    if IsQuot(P^) then
    begin
      AQuot := P^;
      MoveToNextSymbol;
      AToken.Buffer := P;
      MoveUntilQuotOrTag(AQuot);
      AToken.BufferLengthInChars := NativeUInt(P) - NativeUInt(AToken.Buffer);
      MoveToNextSymbol;
    end
    else
      if not CheckForSpecialToken then
      begin
        if IsDelimiter(P^) then
        begin
          AToken.Buffer := P;
          AToken.BufferLengthInChars := 1;
          MoveToNextSymbol;
        end
        else
        begin
          AToken.Buffer := P;
          MoveUntilDelimiter;
          AToken.BufferLengthInChars := NativeUInt(P) - NativeUInt(AToken.Buffer);
        end;
      end;
  end;
end;

function TdxXMLParser.TokenToString(const AToken: TdxXMLToken): AnsiString;
begin
  SetString(Result, AToken.Buffer, AToken.BufferLengthInChars);
end;

{ TdxXMLHelper }

class function TdxXMLHelper.DecodeBoolean(const S: string): Boolean;
var
  AValue: Integer;
begin
  if TryStrToInt(S, AValue) then
    Result := AValue <> 0
  else
    Result := SameText(S, dxAnsiStringToString(sXMLBoolValues[True]));
end;

class function TdxXMLHelper.DecodeString(const S: TdxXMLString): TdxXMLString;
var
  ACode: Integer;
  ALength: Integer;
  I, J: Integer;
begin
  Result := S;
  for I := 0 to XMLServiceCharMapCount - 1 do
    Result := StringReplace(Result, XMLServiceCharMap[I, 1], XMLServiceCharMap[I, 0], [rfReplaceAll]);

  I := 1;
  J := 1;
  ALength := Length(Result);
  while I <= ALength do
  begin
    if (I <= ALength - 6) and (Result[I] = '_') and (Result[I + 1] = 'x') and (Result[I + 6] = '_') and
      TryStrToInt(dxAnsiStringToString('$' + Copy(Result, I + 2, 4)), ACode) then
    begin
      Result[J] := AnsiChar(ACode);
      Inc(I, 6);
    end
    else
      Result[J] := Result[I];

    Inc(I);
    Inc(J);
  end;
  if I <> J then
    SetLength(Result, J - 1);
end;

class function TdxXMLHelper.EncodeBoolean(const Value: Boolean): TdxXMLString;
begin
  Result := sXMLBoolValues[Value];
end;

class function TdxXMLHelper.EncodeString(const S: TdxXMLString; ARemoveBreakLines: Boolean): TdxXMLString;
var
  ACode: Byte;
  I: Integer;
begin
  Result := S;

  if ARemoveBreakLines then
  begin
    Result := StringReplace(Result, AnsiString(#13#10), AnsiString(' '), [rfReplaceAll]);
    Result := StringReplace(Result, AnsiString(#13), AnsiString(' '), [rfReplaceAll]);
    Result := StringReplace(Result, AnsiString(#10), AnsiString(' '), [rfReplaceAll]);
  end;

  for I := XMLServiceCharMapCount - 1 downto 0 do
    Result := StringReplace(Result, XMLServiceCharMap[I, 0], XMLServiceCharMap[I, 1], [rfReplaceAll]);

  I := 1;
  while I <= Length(Result) do
  begin
    ACode := Byte(Result[I]);
    if ACode > $1F then
      Inc(I)
    else
    begin
      Delete(Result, I, 1);
      Insert('_x' + dxStringToAnsiString(IntToHex(ACode, 4)) + '_', Result, I);
      Inc(I, 7);
    end;
  end;
end;

class function TdxXMLHelper.IsBoolean(const S: TdxXMLString): Boolean;
begin
  Result := SameText(sXMLBoolValues[False], S) or SameText(sXMLBoolValues[True], S);
end;

class function TdxXMLHelper.IsPreserveSpacesNeeded(const S: TdxXMLString): Boolean;
var
  I, L: Integer;
begin
  Result := False;
  L := Length(S);
  if L > 0 then
  begin
    Result := (S[1] in [#9, #10, #13, ' ']) or (S[L] in [#9, #10, #13, ' ']);
    if not Result then
    begin
      for I := 1 to Length(S) do
        if S[I] in [#13, #10] then
        begin
          Result := True;
          Break;
        end;
    end;
  end;
end;

{ TdxXMLNodeAttribute }

function TdxXMLNodeAttribute.GetDataSize: Integer;
begin
  Result := Length(Name) + Length(Value) + 4;
end;

function TdxXMLNodeAttribute.ToAnsiString: TdxXMLString;
begin
  Result := ' ' + Name + '="' + FValue + '"';
end;

function TdxXMLNodeAttribute.GetValue: TdxXMLString;
begin
  Result := TdxXMLHelper.DecodeString(FValue);
end;

function TdxXMLNodeAttribute.GetValueAsBoolean: Boolean;
begin
  Result := TdxXMLHelper.DecodeBoolean(ValueAsString);
end;

function TdxXMLNodeAttribute.GetValueAsFloat: Double;
var
  AFormatSettings: TFormatSettings;
begin
  FillChar(AFormatSettings, SizeOf(AFormatSettings), 0);
  AFormatSettings.DecimalSeparator := '.';
  Result := StrToFloat(ValueAsString, AFormatSettings);
end;

function TdxXMLNodeAttribute.GetValueAsInteger: Integer;
begin
  if not TryStrToInt(ValueAsString, Result) then
  begin
    if TdxXMLHelper.IsBoolean(Value) then
      Result := Ord(ValueAsBoolean)
    else
      raise EConvertError.CreateResFmt(@SInvalidInteger, [Value]);
  end;
end;

function TdxXMLNodeAttribute.GetValueAsString: string;
begin
  Result := dxXMLStringToString(Value);
end;

procedure TdxXMLNodeAttribute.SetValue(const Value: TdxXMLString);
begin
  FValue := TdxXMLHelper.EncodeString(Value, True);
end;

procedure TdxXMLNodeAttribute.SetValueAsBoolean(AValue: Boolean);
begin
  ValueAsInteger := Ord(AValue);
end;

procedure TdxXMLNodeAttribute.SetValueAsFloat(const AValue: Double);
var
  AFormatSettings: TFormatSettings;
begin
  FillChar(AFormatSettings, SizeOf(AFormatSettings), 0);
  AFormatSettings.DecimalSeparator := '.';
  ValueAsString := FloatToStr(AValue, AFormatSettings);
end;

procedure TdxXMLNodeAttribute.SetValueAsInteger(AValue: Integer);
begin
  ValueAsString := IntToStr(AValue);
end;

procedure TdxXMLNodeAttribute.SetValueAsString(const AValue: string);
begin
  Value := dxStringToAnsiString(AValue);
end;

{ TdxXMLNodeAttributes }

function TdxXMLNodeAttributes.Add: TcxDoublyLinkedObject;
begin
  Result := inherited Add;
  Inc(FCount);
end;

function TdxXMLNodeAttributes.Add(const AttrName: TdxXMLString): TdxXMLNodeAttribute;
begin
  Result := TdxXMLNodeAttribute(Add);
  Result.Name := AttrName;
end;

function TdxXMLNodeAttributes.Add(const AttrName: TdxXMLString; AValue: Boolean): TdxXMLNodeAttribute;
begin
  Result := Add(AttrName);
  Result.ValueAsBoolean := AValue;
end;

function TdxXMLNodeAttributes.Add(const AttrName: TdxXMLString; AValue: Integer): TdxXMLNodeAttribute;
begin
  Result := Add(AttrName);
  Result.ValueAsInteger := AValue;
end;

function TdxXMLNodeAttributes.Add(const AttrName: TdxXMLString; const AValue: TdxXMLString): TdxXMLNodeAttribute;
begin
  Result := Add(AttrName);
  Result.Value := AValue;
end;

function TdxXMLNodeAttributes.Add(const AttrName: TdxXMLString; const AValue: TdxUnicodeString): TdxXMLNodeAttribute;
begin
  Result := Add(AttrName, dxUnicodeStringToXMLString(AValue));
end;

procedure TdxXMLNodeAttributes.Delete(const AAttrName: TdxXMLString);
var
  AAttr: TdxXMLNodeAttribute;
begin
  if Find(AAttrName, AAttr) then
    Remove(AAttr);
end;

function TdxXMLNodeAttributes.Find(const AAttrName: TdxXMLString; out AAttr: TdxXMLNodeAttribute): Boolean;
begin
  Result := False;
  AAttr := First;
  while AAttr <> nil do
  begin
    Result := SameText(AAttr.Name, AAttrName);
    if Result then
      Break;
    AAttr := TdxXMLNodeAttribute(AAttr.Next);
  end;
end;

function TdxXMLNodeAttributes.GetValue(const AAttrName, ADefaultValue: TdxXMLString): TdxXMLString;
var
  AAttr: TdxXMLNodeAttribute;
begin
  if Find(AAttrName, AAttr) then
    Result := AAttr.Value
  else
    Result := ADefaultValue;
end;

function TdxXMLNodeAttributes.GetValueAsBoolean(const AAttrName: TdxXMLString; ADefaultValue: Boolean = False): Boolean;
var
  AAttr: TdxXMLNodeAttribute;
begin
  if Find(AAttrName, AAttr) then
    Result := AAttr.ValueAsBoolean
  else
    Result := ADefaultValue;
end;

function TdxXMLNodeAttributes.GetValueAsFloat(const AAttrName: TdxXMLString; const ADefaultValue: Double = 0): Double;
var
  AAttr: TdxXMLNodeAttribute;
begin
  if Find(AAttrName, AAttr) then
    Result := AAttr.ValueAsFloat
  else
    Result := ADefaultValue;
end;

function TdxXMLNodeAttributes.GetValueAsInteger(const AAttrName: TdxXMLString; ADefaultValue: Integer = 0): Integer;
var
  AAttr: TdxXMLNodeAttribute;
begin
  Result := ADefaultValue;
  if Find(AAttrName, AAttr) then
  try
    Result := AAttr.ValueAsInteger;
  except
    Result := ADefaultValue;
  end;
end;

function TdxXMLNodeAttributes.GetValueAsString(const AAttrName: TdxXMLString): string;
begin
  Result := dxXMLStringToString(GetValue(AAttrName));
end;

function TdxXMLNodeAttributes.GetValueAsUnicodeString(const AAttrName: TdxXMLString): TdxUnicodeString;
begin
  Result := dxXMLStringToUnicodeString(GetValue(AAttrName));
end;

procedure TdxXMLNodeAttributes.SetValue(const AAttrName, AValue: TdxXMLString);
begin
  GetAttr(AAttrName).Value := AValue;
end;

procedure TdxXMLNodeAttributes.SetValueAsBoolean(const AAttrName: TdxXMLString; AValue: Boolean);
begin
  GetAttr(AAttrName).ValueAsBoolean := AValue;
end;

procedure TdxXMLNodeAttributes.SetValueAsFloat(const AAttrName: TdxXMLString; const AValue: Double);
begin
  GetAttr(AAttrName).ValueAsFloat := AValue;
end;

procedure TdxXMLNodeAttributes.SetValueAsInteger(const AAttrName: TdxXMLString; AValue: Integer);
begin
  GetAttr(AAttrName).ValueAsInteger := AValue;
end;

procedure TdxXMLNodeAttributes.SetValueAsString(const AAttrName: TdxXMLString; const AValue: string);
begin
  GetAttr(AAttrName).ValueAsString := AValue;
end;

procedure TdxXMLNodeAttributes.SetValueAsUnicodeString(const AAttrName: TdxXMLString; const AValue: TdxUnicodeString);
begin
  SetValue(AAttrName, dxUnicodeStringToXMLString(AValue));
end;

procedure TdxXMLNodeAttributes.Remove(ALinkedObject: TcxDoublyLinkedObject);
begin
  inherited Remove(ALinkedObject);
  Dec(FCount);
end;

function TdxXMLNodeAttributes.CreateLinkedObject: TcxDoublyLinkedObject;
begin
  Result := TdxXMLNodeAttribute.Create;
end;

function TdxXMLNodeAttributes.GetAsText: TdxXMLString;
var
  AAttribute: TdxXMLNodeAttribute;
begin
  Result := '';
  if Count > 0 then
  begin
    AAttribute := First;
    while AAttribute <> nil do
    begin
      Result := Result + AAttribute.ToAnsiString;
      AAttribute := TdxXMLNodeAttribute(AAttribute.Next);
    end;
  end;
end;

function TdxXMLNodeAttributes.GetAttr(const AAttrName: TdxXMLString): TdxXMLNodeAttribute;
begin
  if not Find(AAttrName, Result) then
  begin
    Result := TdxXMLNodeAttribute(Add);
    Result.FName := AAttrName;
  end;
end;

function TdxXMLNodeAttributes.GetFirst: TdxXMLNodeAttribute;
begin
  Result := TdxXMLNodeAttribute(inherited First);
end;

function TdxXMLNodeAttributes.GetLast: TdxXMLNodeAttribute;
begin
  Result := TdxXMLNodeAttribute(inherited Last);
end;

{ TdxXMLNode }

constructor TdxXMLNode.Create(AOwner: IdxTreeOwner);
begin
  inherited Create(AOwner);
  FAttributes := TdxXMLNodeAttributes.Create;
  FAttributes.FNode := Self; 
end;

destructor TdxXMLNode.Destroy;
begin
  FreeAndNil(FAttributes);
  inherited Destroy;
end;

function TdxXMLNode.AddChild(const ATagName: TdxXMLString): TdxXMLNode;
begin
  Result := inherited AddChild as TdxXMLNode;
  Result.FName := ATagName;
end;

function TdxXMLNode.AddChild(const ATagName, ANamespaceURI: TdxXMLString): TdxXMLNode;
begin
  Result := AddChild(ATagName);
  Result.FNamespaceURI := ANamespaceURI;
end;

procedure TdxXMLNode.Clear;
begin
  inherited Clear;
  Attributes.Clear;
end;

procedure TdxXMLNode.SetAttribute(const AttrName: TdxXMLString; const AValue: Variant);
begin
  Attributes.SetValue(AttrName, dxWideStringToXMLString(dxVariantToWideString(AValue)));
end;

function TdxXMLNode.FindChild(const AName: TdxXMLString; out ANode: TdxXMLNode): Boolean;
begin
  ANode := First;
  while (ANode <> nil) and not SameText(AName, ANode.Name) do
    ANode := ANode.Next;
  Result := ANode <> nil;
end;

function TdxXMLNode.FindChild(const AName: TdxXMLString): TdxXMLNode;
begin
  if not FindChild(AName, Result) then
    Result := nil;
end;

function TdxXMLNode.FindChild(const ANamesHierarchy: array of TdxXMLString; out ANode: TdxXMLNode): Boolean;
var
  I: Integer;
begin
  ANode := Self;
  for I := 0 to Length(ANamesHierarchy) - 1 do
  begin
    if not ANode.FindChild(ANamesHierarchy[I], ANode) then
    begin
      ANode := nil;
      Break;
    end;
  end;
  Result := (Length(ANamesHierarchy) > 0) and (ANode <> nil);
end;

procedure TdxXMLNode.ForEach(AProc: TdxXMLNodeForEachProc; AUserData: Pointer);
var
  ANode: TdxXMLNode;
begin
  ANode := First;
  while ANode <> nil do
  begin
    AProc(ANode, AUserData);
    ANode := ANode.Next;
  end;
end;

procedure TdxXMLNode.CheckEncodedText(var AText: TdxXMLString);
begin
  if TdxXMLHelper.IsPreserveSpacesNeeded(AText) then
    Attributes.SetValue(sXMLSpaceModeAttr, sXMLSpaceModePreserve)
  else
    Attributes.Delete(sXMLSpaceModeAttr);
end;

function TdxXMLNode.GetAttributesSize: Integer;
begin
  Result := Length(AttributesAsText) + Byte(not HasData) + 1;  // for '>' or '/>'
end;

function TdxXMLNode.GetChildrenSize: Integer;
var
  ANode: TdxXMLNode;
begin
  Result := 0;
  ANode := First as TdxXMLNode;
  while ANode <> nil do
  begin
    Inc(Result, ANode.GetDataSize);
    ANode := ANode.Next;
  end;
end;

function TdxXMLNode.GetDataSize: Integer;
begin
  Result := GetChildrenSize;
  if (Result = 0) and (Attributes.Count = 0) and HasData then
  begin
    Result := Length(FName) * 2 + 5 + Length(FText);
    Exit;
  end;
  Inc(Result, Length(FName) + 1);  // '<' tag
  if HasData then
    Inc(Result, Length(FName) + 3); // '<', '/>' tags
  if Length(FText) > 0 then
    Inc(Result, Length(FText));
  Inc(Result, GetAttributesSize);
end;

function TdxXMLNode.HasData: Boolean;
begin
  Result := (First <> nil) or (Length(FText) > 0);
end;

procedure TdxXMLNode.ReadData(AStream: TStream);
begin
  //nothing to do
end;

procedure TdxXMLNode.WriteAttributes(AStream: TStream);
const
  EndTag: array[Boolean] of TdxXMLString = ('/>', '>');
begin
  WriteString(AStream, AttributesAsText + EndTag[HasData]);
end;

procedure TdxXMLNode.WriteChildren(AStream: TStream);
var
  ANode: TdxXMLNode;
begin
  ANode := First as TdxXMLNode;
  while ANode <> nil do
  begin
    ANode.WriteData(AStream);
    ANode := TdxXMLNode(ANode.Next);
  end;
end;

procedure TdxXMLNode.WriteData(AStream: TStream);
begin

  if (First = nil) and (Attributes.Count = 0) and HasData then
  begin
    WriteString(AStream, '<' + Name + '>' + FText + '</' + Name + '>');
    Exit;
  end;
  WriteString(AStream, '<' + Name);
  WriteAttributes(AStream);
  WriteChildren(AStream);
  if HasData then
  begin
    if Length(FText) > 0 then
      WriteString(AStream, FText);
    WriteString(AStream, '</' + Name + '>');
  end;

end;

procedure TdxXMLNode.WriteString(AStream: TStream; const AString: TdxXMLString);
begin
  if Length(AString) > 0 then
    AStream.WriteBuffer(AString[1], Length(AString));
end;

function TdxXMLNode.GetAttributesAsText: TdxXMLString;
begin
  Result := Attributes.GetAsText;
end;

function TdxXMLNode.GetDocument: TdxXMLDocument;
begin
  Result := TdxXMLDocument(FOwner.GetOwner);
end;

function TdxXMLNode.GetFirst: TdxXMLNode;
begin
  Result := TdxXMLNode(inherited First);
end;

function TdxXMLNode.GetItem(Index: Integer): TdxXMLNode;
begin
  Result := TdxXMLNode(inherited Items[Index]);
end;

function TdxXMLNode.GetNext: TdxXMLNode;
begin
  Result := TdxXMLNode(inherited Next);
end;

function TdxXMLNode.GetParent: TdxXMLNode;
begin
  Result := TdxXMLNode(inherited Parent);
end;

function TdxXMLNode.GetText: TdxXMLString;
begin
  Result := TdxXMLHelper.DecodeString(FText);
end;

function TdxXMLNode.GetTextAsUnicodeString: TdxUnicodeString;
begin
  Result := dxXMLStringToUnicodeString(Text);
end;

procedure TdxXMLNode.SetText(const AValue: TdxXMLString);
begin
  FText := TdxXMLHelper.EncodeString(AValue, False);
  CheckEncodedText(FText);
end;

procedure TdxXMLNode.SetTextAsUnicodeString(const Value: TdxUnicodeString);
begin
  Text := dxUnicodeStringToXMLString(Value);
end;

{ TdxXMLRootNode }

function TdxXMLRootNode.GetDataSize: Integer;
begin
  Result := GetChildrenSize + Length(GetHeaderText);
end;

function TdxXMLRootNode.GetHeaderText: TdxXMLString;
begin
  Result := '';
  if (Length(Document.Version) > 0) or (Length(Document.Encoding) > 0) or (Length(Document.StandAlone) > 0) then
  begin
    Result := '<?xml';
    if Length(Document.Version) > 0 then
      Result := Result + ' version="' + Document.Version + '"';
    if Length(Document.Encoding) > 0 then
      Result := Result + ' encoding="' + Document.Encoding + '"';
    if Length(Document.Standalone) > 0 then
      Result := Result + ' standalone="' + Document.Standalone + '"';
    Result := Result + '?>' + dxCRLF;
  end;
end;

procedure TdxXMLRootNode.WriteData(AStream: TStream);
begin
  WriteString(AStream, GetHeaderText);
  WriteChildren(AStream);
end;

{ TdxXMLDocument }

constructor TdxXMLDocument.Create(AOwner: TPersistent);
begin
  inherited Create(AOwner);
  FRoot := TdxXMLRootNode.Create(Self);
  FVersion := sdxDefaultXMLVersion;
  FEncoding := sEncodingUTF8;
end;

destructor TdxXMLDocument.Destroy; 
begin
  FreeAndNil(FRoot);
  inherited Destroy; 
end;

function TdxXMLDocument.AddChild(const ATagName: TdxXMLString): TdxXMLNode;
begin
  Result := AddChild(ATagName, '');
end;

function TdxXMLDocument.AddChild(const ATagName, ANamespaceURI: TdxXMLString): TdxXMLNode;
begin
  Result := Root.AddChild(ATagName, ANamespaceURI);
end;

function TdxXMLDocument.FindChild(const AName: TdxXMLString; out ANode: TdxXMLNode): Boolean;
begin
  Result := Root.FindChild(AName, ANode);
end;

function TdxXMLDocument.FindChild(const ANamesHierarchy: array of TdxXMLString; out ANode: TdxXMLNode): Boolean;
begin
  Result := Root.FindChild(ANamesHierarchy, ANode);
end;

procedure TdxXMLDocument.LoadFromFile(const AFileName: TFileName);
var
  AStream: TFileStream;
begin
  AStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  try
    LoadFromStream(AStream);
  finally
    AStream.Free;
  end;
end;

procedure TdxXMLDocument.LoadFromStream(AStream: TStream);
var
  ABuffer: PAnsiChar;
  ABufferSize: Integer;
  AParser: TdxXMLParser;
begin
  BeginUpdate;
  try
    Root.Clear;
    ABufferSize := AStream.Size - AStream.Position;
    if ABufferSize > 0 then
    begin
      ABuffer := AllocMem(ABufferSize);
      try
        AParser := TdxXMLParser.Create(Self);
        try
          AStream.ReadBuffer(ABuffer^, ABufferSize);
          AParser.Parse(ABuffer, ABufferSize);
        finally
          AParser.Free;
        end;
      finally
        FreeMem(ABuffer);
      end;
    end;
  finally
    EndUpdate;
  end;
end;

procedure TdxXMLDocument.SaveToFile(const AFileName: TFileName);
var
  AStream: TFileStream;
begin
  AStream := TFileStream.Create(AFileName, fmCreate);
  try
    SaveToStream(AStream);
  finally
    AStream.Free;
  end;
end;

procedure TdxXMLDocument.SaveToStream(AStream: TStream);
var
  I: Integer;
begin
  I := AStream.Position;
  AStream.Size := I + Root.GetDataSize;
  AStream.Position := I;
  Root.WriteData(AStream);
end;

procedure TdxXMLDocument.BeforeDelete(ASender: TdxTreeCustomNode);
begin
end;

procedure TdxXMLDocument.BeginUpdate;
begin
end;

function TdxXMLDocument.CanCollapse(ASender: TdxTreeCustomNode): Boolean;
begin
  Result := True;
end;

function TdxXMLDocument.CanExpand(ASender: TdxTreeCustomNode): Boolean;
begin
  Result := True;
end;

procedure TdxXMLDocument.Collapsed(ASender: TdxTreeCustomNode);
begin
end;

procedure TdxXMLDocument.DeleteNode(ASender: TdxTreeCustomNode);
begin
end;

procedure TdxXMLDocument.EndUpdate;
begin
end;

procedure TdxXMLDocument.Expanded(ASender: TdxTreeCustomNode);
begin
end;

function TdxXMLDocument.GetNodesOwner: TPersistent;
begin
  Result := Self;
end;

procedure TdxXMLDocument.LoadChildren(ASender: TdxTreeCustomNode);
begin
end; 

function TdxXMLDocument.GetNodeClass(ARelativeNode: TdxTreeCustomNode): TdxTreeCustomNodeClass;
begin
  Result := TdxXMLNode;
end;

procedure TdxXMLDocument.TreeNotification(ASender: TdxTreeCustomNode; ANotification: TdxTreeNodeNotifications);
begin
end;

end.
